package com.chenu.gamscriptman.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.chenu.gamscriptman.compoent.Global;
import com.chenu.gamscriptman.dao.*;
import com.chenu.gamscriptman.dao.GameDao;
import com.chenu.gamscriptman.domain.Game;
import com.chenu.gamscriptman.domain.Game;
import com.chenu.gamscriptman.domain.GameData;
import com.chenu.gamscriptman.domain.User;
import com.chenu.gamscriptman.domain.noindb.*;
import com.chenu.gamscriptman.service.GameTask;
import com.chenu.gamscriptman.service.NotifyService;
import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import lombok.Data;
import lombok.experimental.Accessors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * @author flyan
 * date 2019-12-29
 * @function 游戏任务
 */
@CacheConfig(cacheNames = "game")   /* 本类使用缓存名称：game */
@Service("gameTask")
public class GameTaskImpl implements GameTask {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /* 请求游戏服务的服务路由键 */
    public static final String SERVICE_ROUTING_KEY = "game";

    /* 消息服务器的游戏消息队列 */
    public static final String QUEUE = "gamscriptman.gametask.queue";

    /* 数据库数据是否被更新了 */
    public static boolean DbIsUpdate = true;

    /* redis模板，用于缓存和处理结果的暂存 */
    @Autowired
    private RedisTemplate jsonRedisTemplate;

    /* gson，用于处理json数据 */
    @Autowired
    private Gson gson;

    /* 其他任务 */
    @Autowired
    NotifyService notifyService;

    /* 事务管理器 */
    @Autowired
    DataSourceTransactionManager transactionManager;

    /* dao */
    @Autowired
    GameDao gameDao;
    @Autowired
    GameTaskDao gameTaskDao;
    @Autowired
    UserDataDao userDataDao;
    @Autowired
    GameDataDao gameDataDao;

    /* 任务能提供的所有功能索引号 */
    public static final int GAME_LIST = 0;                /* 得到所有游戏信息 */
    public static final int GAME_ADD_OR_EDIT_VIEW = 1;    /* 前往游戏添加/编辑页面 */
    public static final int GAME_ADD_OR_UPDATE = 2;       /* 游戏添加或更新 */
    public static final int GAME_REMOVE = 3;              /* 游戏删除 */
    public static final int GAME_COUNT = 4;               /* 得到游戏数量 */
    public static final int GAME_GET = 5;                 /* 得到一条游戏信息，可通过游戏id/游戏号/MAC地址 */

    /* 任务入口，等待一条消息并处理，然后回发一条包含处理结果的消息 */
    @Override
    @RabbitListener(queues = QUEUE)
    public Message task(Message message) {
        Message rs;

        /* 看看想做什么？ */
        switch (message.getType()){
            case GAME_LIST:                 /* 列出所有游戏 */
                logger.debug("游戏任务：GAME_LIST...");
                rs = do_list(message);
                break;

            case GAME_ADD_OR_EDIT_VIEW:     /* 需要前往游戏添加或编辑页面 */
                logger.debug("游戏任务：GAME_ADD_OR_EDIT_VIEW...");
                rs = do_add_or_edit_view(message);
                break;

            case GAME_ADD_OR_UPDATE:        /* 添加或更新一个游戏 */
                logger.debug("游戏任务：GAME_ADD_OR_UPDATE...");
                rs = do_add_or_update(message);
                break;

            case GAME_REMOVE:               /* 删除一个游戏 */
                logger.debug("游戏任务：GAME_REMOVE...");
                rs = do_remove(message);
                break;

            case GAME_COUNT:               /* 得到游戏数量 */
                logger.debug("游戏任务：GAME_COUNT...");
                rs = do_count(message);
                break;

            case GAME_GET:                    /* 得到一条游戏信息 */
                logger.debug("游戏任务：GAME_GET...");
                rs = do_get(message);
                break;

            default:
                rs = do_no_call(message);
                break;
        }

        /* 返回包含处理结果的消息 */
        return rs;
    }

    private Message do_list(Message message) {
        PageBean<Game> data = null;
        Statistics statistics = null;   /* 统计信息 */
        String rtData = null;

        /* 解析消息中的数据 */
        ListMsgParameter<Game> parameter = gson.fromJson(message.getData(), new TypeToken<ListMsgParameter<Game>>() {}.getType());
        if(parameter != null && parameter.getPageBean() != null){    /* 不为null才需要 */
            parameter.setPageBean(new PageBean<Game>(
                    parameter.getPageBean().getCurrent(),
                    parameter.getPageBean().getSize(),
                    true,
                    parameter.getPageBean().getDynamicHopeSize()
            ));
        }

        /* 获取所有游戏名称列表 */
        List<String> gameNameList = new ArrayList<>();
        for (Game game:
                gameDao.selectList(new QueryWrapper<Game>()
                        .select("name"))) {
            gameNameList.add(game.getName());
        }

        /* 检查查询参数 */
        boolean paging = false;     /* 是否需要分页？ */
        if(gameNameList.size() != 0){
            if(parameter == null) { /* 没有任何限制，搜索游戏名称默认为第一个 */
                parameter = new ListMsgParameter<Game>()
                        .setPageBean(null)
                        .setQueryBean(new Game().setName(gameNameList.get(0)));
            } else {
                if(parameter.getQueryBean() == null){
                    parameter.setQueryBean(new Game().setName(gameNameList.get(0)));
                } else if(StringUtils.isEmpty(parameter.getQueryBean().getName())){
                    parameter.setQueryBean(parameter.getQueryBean().setName(gameNameList.get(0)));
                } else if(gameDao.selectCount(new QueryWrapper<Game>()
                        .eq("name", parameter.getQueryBean().getName())) == 0){
                    /* 最后，确认这个游戏是真的存在吗？不存在依旧是选择默认第一个 */
                    parameter.setQueryBean(parameter.getQueryBean().setName(gameNameList.get(0)));
                }
                paging = true;
            }
        } else {
            /* 还没有任何游戏，那么游戏任务也理应没有 */
            parameter = new ListMsgParameter<>();
        }

        /* 检查是否可以使用更快速的缓存 */
        Game queryBean = (Game) jsonRedisTemplate.opsForValue().get(Global.REDIS_SEARCH_GAME);
        PageBean<Game> pageBean = (PageBean<Game>)jsonRedisTemplate.opsForValue().get(Global.REDIS_GAME_PAGES);
        if(new ListMsgParameter<Game>()
                .setEx_int(null)
                .setQueryBean(queryBean)
                .setPageBean(pageBean)
                .equals(parameter) && !DbIsUpdate){
            logger.debug("游戏列表 --> 使用缓存");
            /* 查询条件一样，而且数据库近期没更新，那么不需要去数据库拿了，缓存中有 */
            return MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.GAME_TASK,
                    Message.HANDLER_SUCCESS,
                    "/games"
            );
        }
        logger.debug("游戏列表 --> 数据库查询");

        /* 查询，这一步只有上面的缓存用不到才会执行到 */
        data = findAll(parameter.getPageBean(), parameter.getQueryBean());
        if(paging) data.calcDynamicBar();  /* 计算动态显示条 */

        /* 现在得到统计信息 */
        statistics = new Statistics().
                setNowTotal(gameDao.selectCount(null)).
                setNowUpdated(0);       /* 还没完成今日更新统计@TODO */

        if(data.getRecords() == null){       /* 查询失败了 */
            return MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.GAME_TASK,
                    Message.HANDLER_FAILURE,
                    "游戏任务：GAME_LIST失败......"
            );
        }

        /* 成功处理 */
        if(message.isNeedView()){
            /* 如果需要视图，将结果、参数放入缓存，并设置返回的页面 */
            jsonRedisTemplate.opsForValue().set(Global.REDIS_GAME_PAGES, data);
            jsonRedisTemplate.opsForValue().set(Global.REDIS_SEARCH_GAME, parameter.getQueryBean());
            /* 统计信息 */
            jsonRedisTemplate.opsForValue().set(Global.REDIS_GAME_STATISTICS, statistics);
            /* 以及所有的游戏名 */
            jsonRedisTemplate.opsForValue().set(Global.REDIS_SEARCH_GAME_NAME_LIST, gameNameList);
            rtData = "/games";
        } else {
            /* 不需要视图，将结果转化为json串放到回复消息中 */
            rtData = gson.toJson(new ListResult<Game>().setPageBean(data).setStatistics(statistics));
            logger.warn("--> " + rtData);
        }

        /* 返回结果 */
        return MessageServiceImpl.TaskFinish(
                message,
                (long) MessageServiceImpl.GAME_TASK,
                Message.HANDLER_SUCCESS,
                rtData
        );
    }

    private Message do_add_or_edit_view(Message message) {
        Game game = null;
        Integer id = null;
        String rtData;

        try{
            id = Integer.valueOf(message.getData());
        } catch (Exception e){
            logger.info("用户前往的是添加页面...");
        }

        if(id != null){
            /* 如果有用户id，则说明是前往编辑页面，查询该用户信息 */
            game = gameDao.selectById(id);
            /* 得到映射信息 */
            game.setGameConfigMap(gson.fromJson(game.getConfigMap(), GameMap.class));
            game.setGameDataMap(gson.fromJson(game.getDataMap(), GameMap.class));
        }

        /* 需要视图？ */
        if(message.isNeedView()){
            /* 放入要编辑的用户 */
            jsonRedisTemplate.opsForValue().set(Global.REDIS_EDIT_GAME, game);
            /* 设置页面 */
            rtData = "/game";
        } else {
            /* 不需要视图，将编辑的用户转为json串 */
            rtData = gson.toJson(game);
        }

        /* 这个调用必定成功 */
        return MessageServiceImpl.TaskFinish(
                message,
                (long) MessageServiceImpl.GAME_TASK,
                Message.HANDLER_SUCCESS,
                rtData
        );
    }

    /**
     * 添加/更新一个游戏信息
     * 游戏信息支持自定义映射规则
     * @param message
     * @return
     */
    private Message do_add_or_update(Message message) {
        int rows;
        boolean isInsert = false;
        int handleState;
        String returnData;
        PageBean<Game> data;

        /* 获取事务定义 */
        DefaultTransactionDefinition transDef = new DefaultTransactionDefinition();
        /* 设置事务隔离级别 */
        transDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        /* 得到事务状态并开启事务 */
        TransactionStatus transStatus = transactionManager.getTransaction(transDef);

        /* 解析消息中的游戏信息 */
        try {
            Game game = gson.fromJson(message.getData(), Game.class);

            /* 将所有中文符号(， “”  ：  {}  )转换为英文符号 */
            if(!StringUtils.isEmpty(game.getConfigMap())){
                game.setConfigMap(com.chenu.gamscriptman.utils.StringUtils.replaceChineseCharacter(game.getConfigMap()));
            }
            if(!StringUtils.isEmpty(game.getDataMap())){
                game.setDataMap(com.chenu.gamscriptman.utils.StringUtils.replaceChineseCharacter(game.getDataMap()));
            }


            if (game.getId() == null) isInsert = true;
            if(isInsert) {      /* 添加，进行一些判断 */

                if(StringUtils.isEmpty(game.getName()) || StringUtils.isEmpty(game.getConfigMap()) ||
                    game.getConfigMap().equals("{\"map\": {}}")){
                    /* 添加一个游戏信息，至少要给出游戏名称以及游戏配置映射信息 */
                    handleState = Message.HANDLER_FAILURE;
                    returnData = "游戏任务：添加一个游戏信息，但没给出其游戏名称和配置映射，无法继续...";
                    transactionManager.rollback(transStatus);
                    /* 这里直接返回错误结果 */
                    return MessageServiceImpl.TaskFinish(
                            message,
                            (long) MessageServiceImpl.GAME_TASK,
                            handleState,
                            returnData
                    );
                } else {    /* 正常情况，可以添加 */

                    QueryWrapper<Game> queryWrapper = new QueryWrapper<>();
                    queryWrapper.eq("name", game.getName());
                    if(gameDao.selectOne(queryWrapper) != null){
                        /* 存在，不能添加 */
                        handleState = Message.HANDLER_FAILURE;
                        returnData = "游戏任务：添加一个游戏信息，但该游戏已经存在，无法继续...";
                        transactionManager.rollback(transStatus);
                        /* 这里直接返回错误结果 */
                        return MessageServiceImpl.TaskFinish(
                                message,
                                (long) MessageServiceImpl.GAME_TASK,
                                handleState,
                                returnData
                        );
                    }
                }
            }

            /* 检查映射信息正不正确 */
            try {
                game.setGameConfigMap(gson.fromJson(game.getConfigMap(), GameMap.class));
                game.setGameDataMap(gson.fromJson(game.getDataMap(), GameMap.class));
                logger.debug(" game config Map --> " + game.getGameConfigMap());
                logger.debug(" game data Map --> " + game.getGameDataMap());
            } catch (JsonParseException je){
                handleState = Message.HANDLER_FAILURE;
                returnData = "游戏任务：映射Json不正确，请检查...";
                transactionManager.rollback(transStatus);
                return MessageServiceImpl.TaskFinish(
                        message,
                        (long) MessageServiceImpl.GAME_TASK,
                        handleState,
                        returnData
                );
            }

            logger.debug(isInsert ? "do_add --> " : "do_update --> " + game);
            /* 执行添加和更新
             */
            if(isInsert){
                rows = save(game);
            } else {
                rows = update(game);
            }

            if(rows > 0){   /* 处理成功 */
                notifyService.notify(message.getSource(),
                        isInsert ? "添加游戏成功！" : "更新游戏（" + game.getName() + "）成功！");
                /* 数据已经更新，得到上次的分页数据和查询数据，模仿调用LIST */
                PageBean<Game> page = (PageBean<Game>) jsonRedisTemplate.opsForValue().get(Global.REDIS_GAME_PAGES);
                Game search_game = (Game) jsonRedisTemplate.opsForValue().get(Global.REDIS_SEARCH_GAME);
                ListMsgParameter<Game> parameter = new ListMsgParameter();
                parameter.setPageBean(page).setQueryBean(search_game);
                message.setData(gson.toJson(parameter));
                /* 提交事务 */
                transactionManager.commit(transStatus);
                return do_list(message);
            } else {        /* 处理失败 */
                handleState = Message.HANDLER_FAILURE;
                returnData = isInsert ? "游戏任务：添加游戏失败，请重试..." : "游戏任务：更新游戏失败，请重试...";
            }

        } catch (Exception e){      /* 解析失败 */
            /* 如果出现异常，回滚事务，所有操作取消 */
            transactionManager.rollback(transStatus);
            handleState = Message.HANDLER_FAILURE;
            returnData = "游戏任务：添加或更新的游戏信息不正确，请检查...";
        }


        /* 返回处理结果 */
        return MessageServiceImpl.TaskFinish(
                message,
                (long) MessageServiceImpl.GAME_TASK,
                handleState,
                returnData
        );
    }

    /* 删除一个游戏，删除游戏的同时，需要将相应的游戏数据和用户数据一起删除 */
    private Message do_remove(Message message) {
        /* 获取事务定义 */
        DefaultTransactionDefinition transDef = new DefaultTransactionDefinition();
        /* 设置事务隔离级别 */
        transDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        /* 得到事务状态并开启事务 */
        TransactionStatus transStatus = transactionManager.getTransaction(transDef);

        int rows = 0;
        try{
            Long id = Long.valueOf(message.getData());
            /* 拿出引用了这个游戏的所有游戏数据 */
            List<GameData> gameDataList = gameDataDao.selectList(new QueryWrapper<GameData>()
                    .select("id")
                    .eq("game_fk", id));
            /* 删除所有引用了这个游戏的游戏数据，同时删除引用它们的所有游戏任务 */
            for (GameData gd :
                    gameDataList) {
                List<com.chenu.gamscriptman.domain.GameTask> tasks = gameTaskDao.selectList(new QueryWrapper<com.chenu.gamscriptman.domain.GameTask>()
                        .select("user_data_fk")
                        .eq("game_data_fk", gd.getId()));
                /* 删除前，必须删除引用了这个游戏数据的游戏任务 */
                gameTaskDao.delete(new UpdateWrapper<com.chenu.gamscriptman.domain.GameTask>()
                        .eq("game_data_fk", gd.getId()));
                /* 用户数据由游戏数据产生，所以，我们也必须删除这条游戏数据产生的用户数据 */
                for (com.chenu.gamscriptman.domain.GameTask t :
                        tasks) {
                    userDataDao.deleteById(t.getUserDataFK());
                }
                /* 最后，删除这个游戏数据 */
                gameDataDao.deleteById(gd.getId());
            }
            /* 现在，才可以删除游戏！ */
            rows = remove(id);
            /* 游戏被修改，那么游戏数据和用户数据就认为被更新过了 */
            GameDataTaskImpl.DbIsUpdate = true;
            UserDataTaskImpl.DbIsUpdate = true;

            /* 成功与否？ */
            if(rows > 0 ){
                /* 给用户一条通知提示成功提示信息 */
                notifyService.notify(message.getSource(), "游戏（" + id + "）删除成功！");
                /* 数据已经更新，得到上次的分页数据和查询数据，模仿调用LIST */
                PageBean<Game> page = (PageBean<Game>) jsonRedisTemplate.opsForValue().get(Global.REDIS_GAME_PAGES);
                Game search_game = (Game) jsonRedisTemplate.opsForValue().get(Global.REDIS_SEARCH_GAME);
                ListMsgParameter<Game> parameter = new ListMsgParameter();
                parameter.setPageBean(page).setQueryBean(search_game);
                message.setData(gson.toJson(parameter));
                /* 提交事务 */
                transactionManager.commit(transStatus);
                return do_list(message);
            } else {
                transactionManager.rollback(transStatus);
                return MessageServiceImpl.TaskFinish(
                        message,
                        (long) MessageServiceImpl.GAME_TASK,
                        Message.HANDLER_FAILURE,
                        "游戏任务：删除失败，这个游戏可能不存在或已经被移除，请重试..."
                );
            }

        } catch (Exception e) {
            /* 如果出现异常，回滚事务，所有操作取消 */
            transactionManager.rollback(transStatus);
            return MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.GAME_TASK,
                    Message.HANDLER_FAILURE,
                    "游戏任务：删除失败，你未指明任何游戏号"
            );
        }
    }

    private Message do_count(Message message) {
        return MessageServiceImpl.TaskFinish(
                message,
                (long) MessageServiceImpl.USERDATA_TASK,
                Message.HANDLER_SUCCESS,
                String.valueOf(gameDao.selectCount(null))
        );
    }

    private Message do_get(Message message) {
        return do_no_call(message);
    }

    private Message do_no_call(Message message) {
        return MessageServiceImpl.TaskFinish(
                message,
                (long) MessageServiceImpl.GAME_TASK,
                Message.HANDLER_FAILURE,
                "游戏任务：你请求了一个不存在的功能服务......"
        );
    }

    @Override
    public PageBean<Game> findAll(PageBean<Game> pageBean, Game queryBean) {
        QueryWrapper<Game> queryWrapper = null;
        DbIsUpdate = false;

        /* 封装条件 */
        if(queryBean != null){
            queryWrapper = new QueryWrapper<>();
            if(queryBean.getName() != null){        /* 游戏名称 */
                queryWrapper.eq("name", queryBean.getName());
            }
        }

        /* 好了，可以从数据库中开始查询了 */
        if(pageBean == null){
            pageBean = new PageBean<>();
            pageBean.setRecords(gameDao.selectList(queryWrapper));
        } else {
            pageBean = gameDao.selectPage(pageBean, queryWrapper);
        }

        /* 解析映射 --- 因为映射使用JSON字符串存储的，所以只需要转换一下即可
         * 这里无需检查转换是否成功，因为在添加和更新游戏的时候已经做过判断了，
         * 除非服务器被人盗取或爆炸了
         */
        for (Game game:
             pageBean.getRecords()) {
            game.setGameConfigMap(gson.fromJson(game.getConfigMap(), GameMap.class));
            game.setGameDataMap(gson.fromJson(game.getDataMap(), GameMap.class));
            logger.debug("--> " + game.getGameConfigMap());
            logger.debug("--> " + game.getGameDataMap());
            Collection<GameMapValue> values = game.getGameConfigMap().getMap().values();
            Set<String> keys = game.getGameConfigMap().getMap().keySet();
            game.getGameConfigMap().getMap().entrySet();
        }

        
        return pageBean;
    }


    @Override
    @CacheEvict(key = "#id")
    public int remove(Long id) {
        DbIsUpdate = true;
        return gameDao.deleteById(id);
    }

    @Override
    public int save(Game record) {
        DbIsUpdate = true;
        return gameDao.insert(record);
    }

    @Override
    @Cacheable(
            key = "#id",                               /* 缓存key使用用户的id */
            cacheManager = "jsonRedisCacheManager",     /* 使用json缓存管理器 */
            unless = "#result == null"                  /* 当没查到时，不缓存 */
    )
    public Game findById(Long id) {
        return gameDao.selectById(id);
    }

    @Override
    @CachePut(key = "#record.id")
    public int update(Game record) {
        DbIsUpdate = true;
        return gameDao.updateById(record);
    }

    @Override
    public int saveOrUpdate(Game record) {
        DbIsUpdate = true;
        int rows = 0;
        // 根据是否有id决定是添加还是更新
        if(record.getId() == null){
            rows = save(record);
        } else {
            rows = update(record);
        }
        return rows;
    }
}

